package com.wt.admin.service.ai.impl.agents.easyai.api;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

public class EasyAIApi {

    public static final String REQUEST_BODY_NULL_ERROR = "The request body can not be null.";
    private static final Log logger = LogFactory.getLog(EasyAIApi.class);
    private static final String DEFAULT_BASE_URL = "http://localhost:11434";
    private final RestClient restClient;
    private final WebClient webClient;

    public static EasyAIApi.Builder builder() {
        return new EasyAIApi.Builder();
    }

    private EasyAIApi(String baseUrl, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
        Consumer<HttpHeaders> defaultHeaders = (headers) -> {
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAccept(List.of(MediaType.APPLICATION_JSON));
        };
        this.restClient = restClientBuilder.clone().baseUrl(baseUrl).defaultHeaders(defaultHeaders).defaultStatusHandler(responseErrorHandler).build();
        this.webClient = webClientBuilder.clone().baseUrl(baseUrl).defaultHeaders(defaultHeaders).build();
    }

    public EasyAIApi.ChatResponse chat(EasyAIApi.ChatRequest chatRequest) {
        Assert.notNull(chatRequest, "The request body can not be null.");
        Assert.isTrue(!chatRequest.stream(), "Stream mode must be disabled.");
        return (EasyAIApi.ChatResponse)((RestClient.RequestBodySpec)this.restClient.post().uri("/api/chat", new Object[0])).body(chatRequest).retrieve().body(EasyAIApi.ChatResponse.class);
    }

    public Flux<EasyAIApi.ChatResponse> streamingChat(EasyAIApi.ChatRequest chatRequest) {
        Assert.notNull(chatRequest, "The request body can not be null.");
        Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true.");
        AtomicBoolean isInsideTool = new AtomicBoolean(false);
        return ((WebClient.RequestBodySpec)this.webClient.post().uri("/api/chat", new Object[0])).body(Mono.just(chatRequest), EasyAIApi.ChatRequest.class).retrieve().bodyToFlux(EasyAIApi.ChatResponse.class).map((chunk) -> {
            if (EasyAIApiHelper.isStreamingToolCall(chunk)) {
                isInsideTool.set(true);
            }

            return chunk;
        }).windowUntil((chunk) -> {
            if (isInsideTool.get() && EasyAIApiHelper.isStreamingDone(chunk)) {
                isInsideTool.set(false);
                return true;
            } else {
                return !isInsideTool.get();
            }
        }).concatMapIterable((window) -> {
            Mono<EasyAIApi.ChatResponse> monoChunk = window.reduce(new EasyAIApi.ChatResponse(), (previous, current) -> EasyAIApiHelper.merge(previous, current));
            return List.of(monoChunk);
        }).flatMap((mono) -> mono).handle((data, sink) -> {
            if (logger.isTraceEnabled()) {
                logger.trace(data);
            }

            sink.next(data);
        });
    }

    public EasyAIApi.EmbeddingsResponse embed(EasyAIApi.EmbeddingsRequest embeddingsRequest) {
        Assert.notNull(embeddingsRequest, "The request body can not be null.");
        return (EasyAIApi.EmbeddingsResponse)((RestClient.RequestBodySpec)this.restClient.post().uri("/api/embed", new Object[0])).body(embeddingsRequest).retrieve().body(EasyAIApi.EmbeddingsResponse.class);
    }

    public EasyAIApi.ListModelResponse listModels() {
        return (EasyAIApi.ListModelResponse)this.restClient.get().uri("/api/tags", new Object[0]).retrieve().body(EasyAIApi.ListModelResponse.class);
    }

    public EasyAIApi.ShowModelResponse showModel(EasyAIApi.ShowModelRequest showModelRequest) {
        Assert.notNull(showModelRequest, "showModelRequest must not be null");
        return (EasyAIApi.ShowModelResponse)((RestClient.RequestBodySpec)this.restClient.post().uri("/api/show", new Object[0])).body(showModelRequest).retrieve().body(EasyAIApi.ShowModelResponse.class);
    }

    public ResponseEntity<Void> copyModel(EasyAIApi.CopyModelRequest copyModelRequest) {
        Assert.notNull(copyModelRequest, "copyModelRequest must not be null");
        return ((RestClient.RequestBodySpec)this.restClient.post().uri("/api/copy", new Object[0])).body(copyModelRequest).retrieve().toBodilessEntity();
    }

    public ResponseEntity<Void> deleteModel(EasyAIApi.DeleteModelRequest deleteModelRequest) {
        Assert.notNull(deleteModelRequest, "deleteModelRequest must not be null");
        return ((RestClient.RequestBodySpec)this.restClient.method(HttpMethod.DELETE).uri("/api/delete", new Object[0])).body(deleteModelRequest).retrieve().toBodilessEntity();
    }

    public Flux<EasyAIApi.ProgressResponse> pullModel(EasyAIApi.PullModelRequest pullModelRequest) {
        Assert.notNull(pullModelRequest, "pullModelRequest must not be null");
        Assert.isTrue(pullModelRequest.stream(), "Request must set the stream property to true.");
        return ((WebClient.RequestBodySpec)this.webClient.post().uri("/api/pull", new Object[0])).bodyValue(pullModelRequest).retrieve().bodyToFlux(EasyAIApi.ProgressResponse.class);
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record Message(EasyAIApi.Message.Role role, String content, List<String> images, List<EasyAIApi.Message.ToolCall> toolCalls) {
        public Message(@JsonProperty("role") EasyAIApi.Message.Role role, @JsonProperty("content") String content, @JsonProperty("images") List<String> images, @JsonProperty("tool_calls") List<EasyAIApi.Message.ToolCall> toolCalls) {
            this.role = role;
            this.content = content;
            this.images = images;
            this.toolCalls = toolCalls;
        }

        public static EasyAIApi.Message.Builder builder(EasyAIApi.Message.Role role) {
            return new EasyAIApi.Message.Builder(role);
        }

        @JsonProperty("role")
        public EasyAIApi.Message.Role role() {
            return this.role;
        }

        @JsonProperty("content")
        public String content() {
            return this.content;
        }

        @JsonProperty("images")
        public List<String> images() {
            return this.images;
        }

        @JsonProperty("tool_calls")
        public List<EasyAIApi.Message.ToolCall> toolCalls() {
            return this.toolCalls;
        }

        public static enum Role {
            @JsonProperty("system")
            SYSTEM,
            @JsonProperty("user")
            USER,
            @JsonProperty("assistant")
            ASSISTANT,
            @JsonProperty("tool")
            TOOL;
        }

        @JsonInclude(JsonInclude.Include.NON_NULL)
        public static record ToolCall(EasyAIApi.Message.ToolCallFunction function) {
            public ToolCall(@JsonProperty("function") EasyAIApi.Message.ToolCallFunction function) {
                this.function = function;
            }

            @JsonProperty("function")
            public EasyAIApi.Message.ToolCallFunction function() {
                return this.function;
            }
        }

        @JsonInclude(JsonInclude.Include.NON_NULL)
        public static record ToolCallFunction(String name, Map<String, Object> arguments) {
            public ToolCallFunction(@JsonProperty("name") String name, @JsonProperty("arguments") Map<String, Object> arguments) {
                this.name = name;
                this.arguments = arguments;
            }

            @JsonProperty("name")
            public String name() {
                return this.name;
            }

            @JsonProperty("arguments")
            public Map<String, Object> arguments() {
                return this.arguments;
            }
        }

        public static class Builder {
            private final EasyAIApi.Message.Role role;
            private String content;
            private List<String> images;
            private List<EasyAIApi.Message.ToolCall> toolCalls;

            public Builder(EasyAIApi.Message.Role role) {
                this.role = role;
            }

            public EasyAIApi.Message.Builder content(String content) {
                this.content = content;
                return this;
            }

            public EasyAIApi.Message.Builder images(List<String> images) {
                this.images = images;
                return this;
            }

            public EasyAIApi.Message.Builder toolCalls(List<EasyAIApi.Message.ToolCall> toolCalls) {
                this.toolCalls = toolCalls;
                return this;
            }

            public EasyAIApi.Message build() {
                return new EasyAIApi.Message(this.role, this.content, this.images, this.toolCalls);
            }
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record ChatRequest(String model, List<EasyAIApi.Message> messages, Boolean stream, Object format, String keepAlive, List<EasyAIApi.ChatRequest.Tool> tools, Map<String, Object> options) {
        public ChatRequest(@JsonProperty("model") String model, @JsonProperty("messages") List<EasyAIApi.Message> messages, @JsonProperty("stream") Boolean stream, @JsonProperty("format") Object format, @JsonProperty("keep_alive") String keepAlive, @JsonProperty("tools") List<EasyAIApi.ChatRequest.Tool> tools, @JsonProperty("options") Map<String, Object> options) {
            this.model = model;
            this.messages = messages;
            this.stream = stream;
            this.format = format;
            this.keepAlive = keepAlive;
            this.tools = tools;
            this.options = options;
        }

        public static EasyAIApi.ChatRequest.Builder builder(String model) {
            return new EasyAIApi.ChatRequest.Builder(model);
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("messages")
        public List<EasyAIApi.Message> messages() {
            return this.messages;
        }

        @JsonProperty("stream")
        public Boolean stream() {
            return this.stream;
        }

        @JsonProperty("format")
        public Object format() {
            return this.format;
        }

        @JsonProperty("keep_alive")
        public String keepAlive() {
            return this.keepAlive;
        }

        @JsonProperty("tools")
        public List<EasyAIApi.ChatRequest.Tool> tools() {
            return this.tools;
        }

        @JsonProperty("options")
        public Map<String, Object> options() {
            return this.options;
        }

        @JsonInclude(JsonInclude.Include.NON_NULL)
        public static record Tool(EasyAIApi.ChatRequest.Tool.Type type, EasyAIApi.ChatRequest.Tool.Function function) {
            public Tool(EasyAIApi.ChatRequest.Tool.Function function) {
                this(EasyAIApi.ChatRequest.Tool.Type.FUNCTION, function);
            }

            public Tool(@JsonProperty("type") EasyAIApi.ChatRequest.Tool.Type type, @JsonProperty("function") EasyAIApi.ChatRequest.Tool.Function function) {
                this.type = type;
                this.function = function;
            }

            @JsonProperty("type")
            public EasyAIApi.ChatRequest.Tool.Type type() {
                return this.type;
            }

            @JsonProperty("function")
            public EasyAIApi.ChatRequest.Tool.Function function() {
                return this.function;
            }

            public static enum Type {
                @JsonProperty("function")
                FUNCTION;
            }

            public static record Function(String name, String description, Map<String, Object> parameters) {
                public Function(String description, String name, String jsonSchema) {
                    this(description, name, ModelOptionsUtils.jsonToMap(jsonSchema));
                }

                public Function(@JsonProperty("name") String name, @JsonProperty("description") String description, @JsonProperty("parameters") Map<String, Object> parameters) {
                    this.name = name;
                    this.description = description;
                    this.parameters = parameters;
                }

                @JsonProperty("name")
                public String name() {
                    return this.name;
                }

                @JsonProperty("description")
                public String description() {
                    return this.description;
                }

                @JsonProperty("parameters")
                public Map<String, Object> parameters() {
                    return this.parameters;
                }
            }
        }

        public static class Builder {
            private final String model;
            private List<EasyAIApi.Message> messages = List.of();
            private boolean stream = false;
            private Object format;
            private String keepAlive;
            private List<EasyAIApi.ChatRequest.Tool> tools = List.of();
            private Map<String, Object> options = Map.of();

            public Builder(String model) {
                Assert.notNull(model, "The model can not be null.");
                this.model = model;
            }

            public EasyAIApi.ChatRequest.Builder messages(List<EasyAIApi.Message> messages) {
                this.messages = messages;
                return this;
            }

            public EasyAIApi.ChatRequest.Builder stream(boolean stream) {
                this.stream = stream;
                return this;
            }

            public EasyAIApi.ChatRequest.Builder format(Object format) {
                this.format = format;
                return this;
            }

            public EasyAIApi.ChatRequest.Builder keepAlive(String keepAlive) {
                this.keepAlive = keepAlive;
                return this;
            }

            public EasyAIApi.ChatRequest.Builder tools(List<EasyAIApi.ChatRequest.Tool> tools) {
                this.tools = tools;
                return this;
            }

            public EasyAIApi.ChatRequest.Builder options(Map<String, Object> options) {
                Objects.requireNonNull(options, "The options can not be null.");
                this.options = EasyAIOptions.filterNonSupportedFields(options);
                return this;
            }

            public EasyAIApi.ChatRequest.Builder options(EasyAIOptions options) {
                Objects.requireNonNull(options, "The options can not be null.");
                this.options = EasyAIOptions.filterNonSupportedFields(options.toMap());
                return this;
            }

            public EasyAIApi.ChatRequest build() {
                return new EasyAIApi.ChatRequest(this.model, this.messages, this.stream, this.format, this.keepAlive, this.tools, this.options);
            }
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record ChatResponse(String model, Instant createdAt, EasyAIApi.Message message, String doneReason, Boolean done, Long totalDuration, Long loadDuration, Integer promptEvalCount, Long promptEvalDuration, Integer evalCount, Long evalDuration) {
        ChatResponse() {
            this((String)null, (Instant)null, (EasyAIApi.Message)null, (String)null, (Boolean)null, (Long)null, (Long)null, (Integer)null, (Long)null, (Integer)null, (Long)null);
        }

        public ChatResponse(@JsonProperty("model") String model, @JsonProperty("created_at") Instant createdAt, @JsonProperty("message") EasyAIApi.Message message, @JsonProperty("done_reason") String doneReason, @JsonProperty("done") Boolean done, @JsonProperty("total_duration") Long totalDuration, @JsonProperty("load_duration") Long loadDuration, @JsonProperty("prompt_eval_count") Integer promptEvalCount, @JsonProperty("prompt_eval_duration") Long promptEvalDuration, @JsonProperty("eval_count") Integer evalCount, @JsonProperty("eval_duration") Long evalDuration) {
            this.model = model;
            this.createdAt = createdAt;
            this.message = message;
            this.doneReason = doneReason;
            this.done = done;
            this.totalDuration = totalDuration;
            this.loadDuration = loadDuration;
            this.promptEvalCount = promptEvalCount;
            this.promptEvalDuration = promptEvalDuration;
            this.evalCount = evalCount;
            this.evalDuration = evalDuration;
        }

        public Duration getTotalDuration() {
            return this.totalDuration() != null ? Duration.ofNanos(this.totalDuration()) : null;
        }

        public Duration getLoadDuration() {
            return this.loadDuration() != null ? Duration.ofNanos(this.loadDuration()) : null;
        }

        public Duration getPromptEvalDuration() {
            return this.promptEvalDuration() != null ? Duration.ofNanos(this.promptEvalDuration()) : null;
        }

        public Duration getEvalDuration() {
            return this.evalDuration() == null ? null : Duration.ofNanos(this.evalDuration());
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("created_at")
        public Instant createdAt() {
            return this.createdAt;
        }

        @JsonProperty("message")
        public EasyAIApi.Message message() {
            return this.message;
        }

        @JsonProperty("done_reason")
        public String doneReason() {
            return this.doneReason;
        }

        @JsonProperty("done")
        public Boolean done() {
            return this.done;
        }

        @JsonProperty("total_duration")
        public Long totalDuration() {
            return this.totalDuration;
        }

        @JsonProperty("load_duration")
        public Long loadDuration() {
            return this.loadDuration;
        }

        @JsonProperty("prompt_eval_count")
        public Integer promptEvalCount() {
            return this.promptEvalCount;
        }

        @JsonProperty("prompt_eval_duration")
        public Long promptEvalDuration() {
            return this.promptEvalDuration;
        }

        @JsonProperty("eval_count")
        public Integer evalCount() {
            return this.evalCount;
        }

        @JsonProperty("eval_duration")
        public Long evalDuration() {
            return this.evalDuration;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record EmbeddingsRequest(String model, List<String> input, Duration keepAlive, Map<String, Object> options, Boolean truncate) {
        public EmbeddingsRequest(String model, String input) {
            this(model, List.of(input), (Duration)null, (Map)null, (Boolean)null);
        }

        public EmbeddingsRequest(@JsonProperty("model") String model, @JsonProperty("input") List<String> input, @JsonProperty("keep_alive") Duration keepAlive, @JsonProperty("options") Map<String, Object> options, @JsonProperty("truncate") Boolean truncate) {
            this.model = model;
            this.input = input;
            this.keepAlive = keepAlive;
            this.options = options;
            this.truncate = truncate;
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("input")
        public List<String> input() {
            return this.input;
        }

        @JsonProperty("keep_alive")
        public Duration keepAlive() {
            return this.keepAlive;
        }

        @JsonProperty("options")
        public Map<String, Object> options() {
            return this.options;
        }

        @JsonProperty("truncate")
        public Boolean truncate() {
            return this.truncate;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record EmbeddingsResponse(String model, List<float[]> embeddings, Long totalDuration, Long loadDuration, Integer promptEvalCount) {
        public EmbeddingsResponse(@JsonProperty("model") String model, @JsonProperty("embeddings") List<float[]> embeddings, @JsonProperty("total_duration") Long totalDuration, @JsonProperty("load_duration") Long loadDuration, @JsonProperty("prompt_eval_count") Integer promptEvalCount) {
            this.model = model;
            this.embeddings = embeddings;
            this.totalDuration = totalDuration;
            this.loadDuration = loadDuration;
            this.promptEvalCount = promptEvalCount;
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("embeddings")
        public List<float[]> embeddings() {
            return this.embeddings;
        }

        @JsonProperty("total_duration")
        public Long totalDuration() {
            return this.totalDuration;
        }

        @JsonProperty("load_duration")
        public Long loadDuration() {
            return this.loadDuration;
        }

        @JsonProperty("prompt_eval_count")
        public Integer promptEvalCount() {
            return this.promptEvalCount;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record Model(String name, String model, Instant modifiedAt, Long size, String digest, EasyAIApi.Model.Details details) {
        public Model(@JsonProperty("name") String name, @JsonProperty("model") String model, @JsonProperty("modified_at") Instant modifiedAt, @JsonProperty("size") Long size, @JsonProperty("digest") String digest, @JsonProperty("details") EasyAIApi.Model.Details details) {
            this.name = name;
            this.model = model;
            this.modifiedAt = modifiedAt;
            this.size = size;
            this.digest = digest;
            this.details = details;
        }

        @JsonProperty("name")
        public String name() {
            return this.name;
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("modified_at")
        public Instant modifiedAt() {
            return this.modifiedAt;
        }

        @JsonProperty("size")
        public Long size() {
            return this.size;
        }

        @JsonProperty("digest")
        public String digest() {
            return this.digest;
        }

        @JsonProperty("details")
        public EasyAIApi.Model.Details details() {
            return this.details;
        }

        @JsonInclude(JsonInclude.Include.NON_NULL)
        @JsonIgnoreProperties(
                ignoreUnknown = true
        )
        public static record Details(String parentModel, String format, String family, List<String> families, String parameterSize, String quantizationLevel) {
            public Details(@JsonProperty("parent_model") String parentModel, @JsonProperty("format") String format, @JsonProperty("family") String family, @JsonProperty("families") List<String> families, @JsonProperty("parameter_size") String parameterSize, @JsonProperty("quantization_level") String quantizationLevel) {
                this.parentModel = parentModel;
                this.format = format;
                this.family = family;
                this.families = families;
                this.parameterSize = parameterSize;
                this.quantizationLevel = quantizationLevel;
            }

            @JsonProperty("parent_model")
            public String parentModel() {
                return this.parentModel;
            }

            @JsonProperty("format")
            public String format() {
                return this.format;
            }

            @JsonProperty("family")
            public String family() {
                return this.family;
            }

            @JsonProperty("families")
            public List<String> families() {
                return this.families;
            }

            @JsonProperty("parameter_size")
            public String parameterSize() {
                return this.parameterSize;
            }

            @JsonProperty("quantization_level")
            public String quantizationLevel() {
                return this.quantizationLevel;
            }
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record ListModelResponse(List<EasyAIApi.Model> models) {
        public ListModelResponse(@JsonProperty("models") List<EasyAIApi.Model> models) {
            this.models = models;
        }

        @JsonProperty("models")
        public List<EasyAIApi.Model> models() {
            return this.models;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record ShowModelRequest(String model, String system, Boolean verbose, Map<String, Object> options) {
        public ShowModelRequest(String model) {
            this(model, (String)null, (Boolean)null, (Map)null);
        }

        public ShowModelRequest(@JsonProperty("model") String model, @JsonProperty("system") String system, @JsonProperty("verbose") Boolean verbose, @JsonProperty("options") Map<String, Object> options) {
            this.model = model;
            this.system = system;
            this.verbose = verbose;
            this.options = options;
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("system")
        public String system() {
            return this.system;
        }

        @JsonProperty("verbose")
        public Boolean verbose() {
            return this.verbose;
        }

        @JsonProperty("options")
        public Map<String, Object> options() {
            return this.options;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record ShowModelResponse(String license, String modelfile, String parameters, String template, String system, EasyAIApi.Model.Details details, List<EasyAIApi.Message> messages, Map<String, Object> modelInfo, Map<String, Object> projectorInfo, List<String> capabilities, Instant modifiedAt) {
        public ShowModelResponse(@JsonProperty("license") String license, @JsonProperty("modelfile") String modelfile, @JsonProperty("parameters") String parameters, @JsonProperty("template") String template, @JsonProperty("system") String system, @JsonProperty("details") EasyAIApi.Model.Details details, @JsonProperty("messages") List<EasyAIApi.Message> messages, @JsonProperty("model_info") Map<String, Object> modelInfo, @JsonProperty("projector_info") Map<String, Object> projectorInfo, @JsonProperty("capabilities") List<String> capabilities, @JsonProperty("modified_at") Instant modifiedAt) {
            this.license = license;
            this.modelfile = modelfile;
            this.parameters = parameters;
            this.template = template;
            this.system = system;
            this.details = details;
            this.messages = messages;
            this.modelInfo = modelInfo;
            this.projectorInfo = projectorInfo;
            this.capabilities = capabilities;
            this.modifiedAt = modifiedAt;
        }

        @JsonProperty("license")
        public String license() {
            return this.license;
        }

        @JsonProperty("modelfile")
        public String modelfile() {
            return this.modelfile;
        }

        @JsonProperty("parameters")
        public String parameters() {
            return this.parameters;
        }

        @JsonProperty("template")
        public String template() {
            return this.template;
        }

        @JsonProperty("system")
        public String system() {
            return this.system;
        }

        @JsonProperty("details")
        public EasyAIApi.Model.Details details() {
            return this.details;
        }

        @JsonProperty("messages")
        public List<EasyAIApi.Message> messages() {
            return this.messages;
        }

        @JsonProperty("model_info")
        public Map<String, Object> modelInfo() {
            return this.modelInfo;
        }

        @JsonProperty("projector_info")
        public Map<String, Object> projectorInfo() {
            return this.projectorInfo;
        }

        @JsonProperty("capabilities")
        public List<String> capabilities() {
            return this.capabilities;
        }

        @JsonProperty("modified_at")
        public Instant modifiedAt() {
            return this.modifiedAt;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record CopyModelRequest(String source, String destination) {
        public CopyModelRequest(@JsonProperty("source") String source, @JsonProperty("destination") String destination) {
            this.source = source;
            this.destination = destination;
        }

        @JsonProperty("source")
        public String source() {
            return this.source;
        }

        @JsonProperty("destination")
        public String destination() {
            return this.destination;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record DeleteModelRequest(String model) {
        public DeleteModelRequest(@JsonProperty("model") String model) {
            this.model = model;
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static record PullModelRequest(String model, boolean insecure, String username, String password, boolean stream) {
        public PullModelRequest(@JsonProperty("model") String model, @JsonProperty("insecure") boolean insecure, @JsonProperty("username") String username, @JsonProperty("password") String password, @JsonProperty("stream") boolean stream) {
            if (!stream) {
                EasyAIApi.logger.warn("Enforcing streaming of the model pull request");
            }

            stream = true;
            this.model = model;
            this.insecure = insecure;
            this.username = username;
            this.password = password;
            this.stream = stream;
        }

        public PullModelRequest(String model) {
            this(model, false, (String)null, (String)null, true);
        }

        @JsonProperty("model")
        public String model() {
            return this.model;
        }

        @JsonProperty("insecure")
        public boolean insecure() {
            return this.insecure;
        }

        @JsonProperty("username")
        public String username() {
            return this.username;
        }

        @JsonProperty("password")
        public String password() {
            return this.password;
        }

        @JsonProperty("stream")
        public boolean stream() {
            return this.stream;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties(
            ignoreUnknown = true
    )
    public static record ProgressResponse(String status, String digest, Long total, Long completed) {
        public ProgressResponse(@JsonProperty("status") String status, @JsonProperty("digest") String digest, @JsonProperty("total") Long total, @JsonProperty("completed") Long completed) {
            this.status = status;
            this.digest = digest;
            this.total = total;
            this.completed = completed;
        }

        @JsonProperty("status")
        public String status() {
            return this.status;
        }

        @JsonProperty("digest")
        public String digest() {
            return this.digest;
        }

        @JsonProperty("total")
        public Long total() {
            return this.total;
        }

        @JsonProperty("completed")
        public Long completed() {
            return this.completed;
        }
    }

    public static class Builder {
        private String baseUrl = "http://localhost:8083/qa";
        private RestClient.Builder restClientBuilder = RestClient.builder();
        private WebClient.Builder webClientBuilder = WebClient.builder();
        private ResponseErrorHandler responseErrorHandler;

        public Builder() {
            this.responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER;
        }

        public EasyAIApi.Builder baseUrl(String baseUrl) {
            Assert.hasText(baseUrl, "baseUrl cannot be null or empty");
            this.baseUrl = baseUrl;
            return this;
        }

        public EasyAIApi.Builder restClientBuilder(RestClient.Builder restClientBuilder) {
            Assert.notNull(restClientBuilder, "restClientBuilder cannot be null");
            this.restClientBuilder = restClientBuilder;
            return this;
        }

        public EasyAIApi.Builder webClientBuilder(WebClient.Builder webClientBuilder) {
            Assert.notNull(webClientBuilder, "webClientBuilder cannot be null");
            this.webClientBuilder = webClientBuilder;
            return this;
        }

        public EasyAIApi.Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) {
            Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null");
            this.responseErrorHandler = responseErrorHandler;
            return this;
        }

        public EasyAIApi build() {
            return new EasyAIApi(this.baseUrl, this.restClientBuilder, this.webClientBuilder, this.responseErrorHandler);
        }
    }
}
